// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#define FASTFLOAT_ALLOWS_LEADING_PLUS 1

#include <nebula/util/value_parsing.h>

#include <string>
#include <utility>

#include <turbo/numeric/float16.h>
#include <stevin/fast_float/fast_float.h>

namespace nebula {
    namespace internal {

        bool StringToFloat(const char *s, size_t length, char decimal_point, float *out) {
            ::stevin::fast_float::parse_options options{
                    ::stevin::fast_float::chars_format::general, decimal_point};
            const auto res =
                    ::stevin::fast_float::from_chars_advanced(s, s + length, *out, options);
            return res.ec == std::errc() && res.ptr == s + length;
        }

        bool StringToFloat(const char *s, size_t length, char decimal_point, double *out) {
            ::stevin::fast_float::parse_options options{
                    ::stevin::fast_float::chars_format::general, decimal_point};
            const auto res =
                    ::stevin::fast_float::from_chars_advanced(s, s + length, *out, options);
            return res.ec == std::errc() && res.ptr == s + length;
        }

        // Half float
        bool StringToFloat(const char *s, size_t length, char decimal_point, uint16_t *out) {
            ::stevin::fast_float::parse_options options{
                    ::stevin::fast_float::chars_format::general, decimal_point};
            float temp_out;
            const auto res =
                    ::stevin::fast_float::from_chars_advanced(s, s + length, temp_out, options);
            const bool ok = res.ec == std::errc() && res.ptr == s + length;
            if (ok) {
                *out = turbo::Float16::from_float(temp_out).bits();
            }
            return ok;
        }

        // ----------------------------------------------------------------------
        // strptime-like parsing

        namespace {

            class StrptimeTimestampParser : public TimestampParser {
            public:
                explicit StrptimeTimestampParser(std::string format)
                        : format_(std::move(format)), have_zone_offset_(false) {
                    // Check for use of %z
                    size_t cur = 0;
                    while (cur < format_.size()) {
                        if (format_[cur] == '%') {
                            if (cur + 1 < format_.size() && format_[cur + 1] == 'z') {
                                have_zone_offset_ = true;
                                break;
                            }
                            cur++;
                        }
                        cur++;
                    }
                }

                bool operator()(const char *s, size_t length, TimeUnit::type out_unit, int64_t *out,
                                bool *out_zone_offset_present = nullptr) const override {
                    if (out_zone_offset_present) {
                        *out_zone_offset_present = have_zone_offset_;
                    }
                    return ParseTimestampStrptime(s, length, format_.c_str(),
                            /*ignore_time_in_day=*/false,
                            /*allow_trailing_chars=*/false, out_unit, out);
                }

                const char *kind() const override { return "strptime"; }

                const char *format() const override { return format_.c_str(); }

            private:
                std::string format_;
                bool have_zone_offset_;
            };

            class ISO8601Parser : public TimestampParser {
            public:
                ISO8601Parser() {}

                bool operator()(const char *s, size_t length, TimeUnit::type out_unit, int64_t *out,
                                bool *out_zone_offset_present = nullptr) const override {
                    return ParseTimestampISO8601(s, length, out_unit, out, out_zone_offset_present);
                }

                const char *kind() const override { return "iso8601"; }
            };

        }  // namespace
    }  // namespace internal

    const char *TimestampParser::format() const { return ""; }

    std::shared_ptr<TimestampParser> TimestampParser::MakeStrptime(std::string format) {
        return std::make_shared<internal::StrptimeTimestampParser>(std::move(format));
    }

    std::shared_ptr<TimestampParser> TimestampParser::MakeISO8601() {
        return std::make_shared<internal::ISO8601Parser>();
    }

}  // namespace nebula
